import os
import json
import logging
import time

from watchdog.observers import Observer
from watchdog.events import (
    FileSystemEventHandler, 
    LoggingEventHandler,
    EVENT_TYPE_MOVED,
    EVENT_TYPE_DELETED,
    EVENT_TYPE_CREATED,
    EVENT_TYPE_MODIFIED
)

from utils.database.sqlite3 import NativeSqlite3Handle

from .models import Files_Records

import sqlite3

def is_exclude_path(path, paths):
    for t_path in paths:
        if path.startswith(t_path):
            return True
    return False


def records_generator(directory, threshold=1024):
    records = []
    for root, dirs, files in os.walk(directory):
        records += [{"directory": root, "name": file,
                     "type": os.path.splitext(file)[-1][1:].lower() if len(os.path.splitext(file)) > 0 else "-"}
                    for file in files]
        records += [{"directory": root, "name": dir,
                     "type": "directory"} for dir in dirs]

        if (len(records) >= threshold):
            yield records
            records = []

    yield records


def get_stat(path):
    try:
        stat = os.stat(path)
    except OSError:
        logging.error(f'get stat error : {path}')
    else:
        size = stat.st_size
        create_time = int(stat.st_ctime)
        update_time = int(stat.st_mtime)
        return size, create_time, update_time

def get_entry(entry):
    entry["path"] = os.path.join(entry['directory'], entry['name'])
    entry['size'],entry['create_time'],entry['update_time'] = get_stat(entry['path'])
    entry['is_dir'] = os.path.isdir(entry['path'])
    return entry


def update_record(name, directory, type, size, create_time, update_time, comments=""):
    try:
        entry = Files_Records.objects.get(name=name, directory=directory)
        logging.info(f"更新路径：{os.path.join(directory, name)}")
    except Files_Records.DoesNotExist:
        logging.info(f"添加路径：{os.path.join(directory, name)}")
        entry = Files_Records(name=name, directory=directory, type=type)
    finally:
        entry.size = size
        entry.create_time = create_time
        entry.update_time = update_time
        entry.comments = comments
        entry.save()


def update_files_records(db, records):

    table_cols = ['name', 'directory', 'type',
                  'size', 'update_time', 'create_time']

    def sql_excape(value):
        if isinstance(value, str):
            return value.replace("'", "''")
        return value

    def generate_value(record, table):
        return "(" + ",".join([f"'{sql_excape(record[col])}'" for col in table]) + ")"

    def generate_insert_sql(table_name, cols, values):
        if len(values) <= 0:
            return
        sep = ',\n'
        return f"replace into {table_name} ({sep.join(cols)}) values {sep.join(values)}"

    values = []
    for record in records:
        try:
            record["size"], record["create_time"], record["update_time"] = get_stat(
                os.path.join(record["directory"], record["name"]))
        except TypeError:
            logging.error(f'get path stat error : {os.path.join(record["directory"], record["name"])}')
            continue
        else:
            values.append(generate_value(record, table_cols))

    sql_command = generate_insert_sql('fs_files_records', table_cols, values)
    if sql_command is not None:
        db.insert(sql_command)


def initialize(config):
    directories = config["directories"]
    db = NativeSqlite3Handle(config["database"])
    for dir in directories:
        for records in records_generator(dir):
            update_files_records(db, records)


def path_split(path):
    path_splits = os.path.split(path)
    path_ext_splits = os.path.splitext(path_splits[1])
    type = "-" if len(
        path_ext_splits) == 1 else path_ext_splits[-1][1:].lower()
    type = "directory" if os.path.isdir(path) else type

    return path_splits[0], path_splits[1], type


class DatabaseFileSystemEventHandler(FileSystemEventHandler):
    def __init__(self, config):
        self.config = config
        self.exclude_paths = self.config['excludes']
        self.exclude_paths.append(os.path.abspath("."))

        logging.info(f"exclude paths: {self.exclude_paths}")

    def dispatch(self, event):
        """Dispatches events to the appropriate methods.

        :param event:
            The event object representing the file system event.
        :type event:
            :class:`FileSystemEvent`
        """
        if self.on_any_event(event) is True:
            {
                EVENT_TYPE_CREATED: self.on_created,
                EVENT_TYPE_DELETED: self.on_deleted,
                EVENT_TYPE_MODIFIED: self.on_modified,
                EVENT_TYPE_MOVED: self.on_moved,
            }[event.event_type](event)

    def on_any_event(self, event):
        return not is_exclude_path(event.src_path, self.exclude_paths)

    def on_created(self, event):
        super(DatabaseFileSystemEventHandler, self).on_created(event)

        path = event.src_path

        self.update_path(path)

    def on_deleted(self, event):
        super(DatabaseFileSystemEventHandler, self).on_deleted(event)

        path = event.src_path
        dir, name, _ = path_split(path)
        try:
            Files_Records.objects.get(name=name, directory=dir).delete()
        except Files_Records.DoesNotExist:
            pass

    def on_modified(self, event):
        super(DatabaseFileSystemEventHandler, self).on_modified(event)
        path = event.src_path

        self.update_path(path)

    def on_moved(self, event):
        super(DatabaseFileSystemEventHandler, self).on_moved(event)
        src_path = event.src_path
        dst_path = event.dest_path

        dir, name, _ = path_split(src_path)
        try:
            Files_Records.objects.get(name=name, directory=dir).delete()
        except Files_Records.DoesNotExist:
            pass
        self.update_path(dst_path)

    def update_path(self, path):
        dir, name, type = path_split(path)
        try:
            size, create_time, update_time = get_stat(path)
        except TypeError:
            logging.error(f"get path stat error : {path}")
        else:
            update_record(
                name=name,
                directory=dir,
                type=type,
                size=size,
                create_time=create_time,
                update_time=update_time
            )


def main_loop(config):
    directories = config["directories"]

    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )

    initialize(config)

    event_handler = LoggingEventHandler()
    n_watches = len(directories)
    observer = Observer()
    watches = [observer.schedule(
        event_handler, directories[i], recursive=True) for i in range(n_watches)]
    for watch in watches:
        observer.add_handler_for_watch(
            DatabaseFileSystemEventHandler(config), watch)
    observer.start()

    try:
        while True:
            time.sleep(1)
    finally:
        observer.stop()
        observer.join()
